//the rootnode implementation
package rootnode

import (
	"errors"
	"io"
	"log"
	"net"
	"os"
	"sync/atomic"

	"util/config"
	"util/log"
	"util/zkwrapper"
)

type Server struct {
	ip    string
	port  string
	id    int
	state int32

	stats   *Stats
	datadir string
	metadir string

	errLog  *log.Logger
	workLog *log.Logger
	infoLog *log.Logger
}

func NewServer() *Server {
	return new(Server)
}

func (s *Server) Start(cfg *config.Config) error {
	//firstly check the config values
	e := s.checkConfig(cfg)
	if e != nil {
		return e
	}
	//log
	e = s.initLog()
	if e != nil {
		return e
	}
	//server stats
	s.stats = NewStats(s.id)

	//start to serve
	return s.listenAndServe()
}

func (s *Server) Shutdown() {
	s.setState(ShuttingDown)
	log.Println("Info: rootnode is shut down..")
}

// server core functions - listenAndServer, serveConn, handlePrimaryWrite, ...
func (s *Server) listenAndServe() error {
	//firstly, listen on the port
	l, errListen := net.Listen("tcp", ":"+s.port)
	if errListen != nil {
		s.errLog.Println("Error: failed to listen ", errListen)
		return errListen
	}

	//then serve the connections
	for {
		conn, errAccept := l.Accept()
		if errAccept != nil {
			s.errLog.Println("Error: failed to accept ", errAccept)
			return errAccept
		}
		if s.getState() == ShuttingDown {
			break
		}

		//now handle the connection
		go func() {
			s.serveConn(conn)
		}()
	}

	//close the socket
	l.Close()
	return nil
}

//serve a single connection
func (s *Server) serveConn(rc net.Conn) {
	s.stats.AddConnection()
	remoteAddr := rc.RemoteAddr().String()
	s.workLog.Println("Info: accepted a connection from " + remoteAddr)

	c, ok := rc.(*net.TCPConn)
	if !ok {
		rc.Close()
		s.errLog.Println("Error: tcp conn convert failed")
	}

	//disable tcp nagle algo
	c.SetNoDelay(true)

	for {
		//in case the server was shut down..
		if s.getState() == ShuttingDown {
			break
		}

		req := NewPacket()
		e := req.ReadFromConn(c)
		if e != nil {
			if e == io.EOF {
				s.workLog.Println("Info: the conn was closed by the peer")
			} else {
				s.errLog.Println("Error: failed to read from the conn, ", e)
			}
			break
		}

		var reply *Packet

		switch req.Opcode {
		case ContainerCreate:
			reply = s.handleContainerCreate(req)

		default:
			s.workLog.Printf("Warn: unknown request cmd:\n", req.Opcode)
		}

		//reponse the client
		e = reply.WriteToConn(c)
		if e != nil {
			s.errLog.Println("Error: have to break the conn, ", e)
			break
		}
	}

	//close the connection
	c.Close()
	s.stats.RemoveConnection()
	s.workLog.Println("Info: closed the connection from " + remoteAddr)
}

func (s *Server) initLog() error {
	var (
		fp  *os.File
		err error
	)
	const LogFileOpt = os.O_RDWR | os.O_CREATE | os.O_APPEND
	logOpt := log.LstdFlags | log.Lmicroseconds

	//err, fatal, warn, info log
	fp, err = os.OpenFile(s.metadir+"/"+ErrLogFileName, LogFileOpt, 0666)
	if err != nil {
		return errors.New("ErrLogFileOpenFailed")
	}
	s.errLog = log.New(fp, "", logOpt)

	fp, err = os.OpenFile(s.metadir+"/"+FatalLogFileName, LogFileOpt, 0666)
	if err != nil {
		return errors.New("FaltalLogFileOpenFailed")
	}
	s.fatalLog = log.New(fp, "", logOpt)

	fp, err = os.OpenFile(s.metadir+"/"+WorkLogFileName, LogFileOpt, 0666)
	if err != nil {
		return errors.New("WorkLogFileOpenFailed")
	}
	s.workLog = log.New(fp, "", logOpt)

	fp, err = os.OpenFile(s.metadir+"/"+InfoLogFileName, LogFileOpt, 0666)
	if err != nil {
		return errors.New("InfoLogFileOpenFailed")
	}
	s.infoLog = log.New(fp, "", logOpt)

	return nil
}

func (s *Server) checkConfig(cfg *config.Config) error {
	return nil
}

//atomic load/set
func (s *Server) setState(state int32) {
	atomic.StoreInt32(&s.state, state)
}
func (s *Server) getState() int32 {
	return atomic.LoadInt32(&s.state)
}

//server operations
func (s *Server) handleContainerCreate(req *Packet) *Packet {
	reply := NewPacket()

	containerName, e := req.parseContainerName()
	if e != nil {
		s.errLog.Println("Error: failed to parse container name - ", e)
		reply.PackErrorReply("ParseArgumentError")
		return reply
	}

	//let's build the container as well as its first partition

	reply.Opcode = OkContainerCreate
	return reply
}
